package jasmine.runner;

import com.gargoylesoftware.htmlunit.ScriptException;
import jasmine.resources.Bookmark;
import org.apache.commons.io.IOUtils;
import org.fitweb.fixture.BrowserController;
import org.fitweb.fixture.XmlFormatter;
import org.openqa.selenium.By;
import org.openqa.selenium.WebElement;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;

import static java.lang.System.getProperty;
import static org.apache.commons.io.FileUtils.writeStringToFile;
import static org.apache.commons.lang.StringUtils.join;
import static org.junit.Assert.assertFalse;

public class JasmineRunner {
    private String[] javascriptRoots;

    public JasmineRunner(String... javascriptRoots) {
        this.javascriptRoots = javascriptRoots;
        extractResources("jasmine-webdriver.html", "jasmine-html.js", "jasmine.css", "jasmine.js", "buildReport.js", "runJasmine.js");
    }

    // TODO - CAS - Aug 10, 2010 - Make browser type a test parameter
    public void runJasmineTests(String... dependencies) throws IOException {
        writeResults("No Report Generated");

        BrowserController controller = new BrowserController(BrowserController.DriverType.html);
        controller.navigate().to(htmlHostPage());
        loadJavascript(controller, absolute(dependencies));

        loadJavascript(controller, "runJasmine.js");

        writeResults(pageContents(controller));
        String report = (String) controller.executeJavascript("return report();");
        assertFalse("Report file: " + htmlHostPage().toString() + "\n" + report, report.contains("FAILURE"));
    }

    private String[] absolute(String... dependencies) {
        ArrayList<String> matchedDependencies = new ArrayList<String>();
        for (String dependency : dependencies) {
            matchedDependencies.add(find(dependency));
        }

        return matchedDependencies.toArray(new String[matchedDependencies.size()]);

    }

    private String find(String dependency) throws RuntimeException {
        for (String root : javascriptRoots) {
            File actualDependency = new File(workingDirectory(), root + File.separator + dependency);
            if (actualDependency.exists()) {
                try {
                    return actualDependency.toURI().toURL().toString();
                } catch (MalformedURLException e) {
                    throw new RuntimeException("Dodgy URL detected when converting URI[" + actualDependency.toURI() + "]", e);
                }
            }
        }
        
        throw new RuntimeException(""
                + "Could not find dependency [" + dependency + "] in the provided javascript roots [\n"
                + join(javascriptRoots, "\n")
                + "\n] which are relative to your working directory [" + workingDirectory() + "]"
        );
    }

    void loadJavascript(BrowserController controller, String... dependencies) {
        ArrayList<String> errors = new ArrayList<String>();
        for (String dependency : dependencies) {
            try {
                controller.executeJavascript("importJavascript('" + dependency + "');");
            } catch (ScriptException javascriptFailure) {
                errors.add(getRootCause(javascriptFailure).getMessage());
            }
        }

        if (!errors.isEmpty()) {
            throw new RuntimeException("Javascript failed to load:\n" + join(errors.toArray(), '\n'));
        }
    }

    private Throwable getRootCause(Throwable exception) {
        if (exception.getCause() == null) return exception;
        return getRootCause(exception.getCause());
    }

    URL htmlHostPage() {
        try {
            return tempFileFor("jasmine-webdriver.html").toURI().toURL();
        } catch (IOException e) {
            throw new RuntimeException("Failed to load Jasmine host page", e);
        }
    }

    private File tempFileFor(String resourceName) {
        return new File(tempDirectory(), resourceName);
    }

    void extractResources(String... resourceNames) {
        for (String resourceName : resourceNames) {
            try {
                InputStream stream = Bookmark.class.getResourceAsStream(resourceName);
                String fileContents = IOUtils.toString(stream);
                writeStringToFile(tempFileFor(resourceName), fileContents);
            } catch (Exception e) {
                throw new RuntimeException("Failed to extract resource [" + resourceName + "] from jar to user temp directory", e);
            }
        }

    }

    void writeResults(String contents) throws IOException {
        File outputFile = new File(outputDirectory(), "test-results.html");
        writeStringToFile(outputFile, contents);
    }


    String pageContents(BrowserController controller) {
        WebElement page = controller.findElement(By.xpath("//html"));
        return XmlFormatter.format(controller.innerHtml(page));
    }

    File outputDirectory() {
        return tempDirectory();
    }

    File tempDirectory() {
        return new File(new File(getProperty("java.io.tmpdir")), "jasmine");
    }

    File workingDirectory() {
        return new File(getProperty("user.dir"));
    }
}